#include "FileSpy.h"

FileSpy::FileSpy(void)
{
}

FileSpy::~FileSpy(void)
{
}

FileSpy* FileSpy::s_fileSpy = NULL;
FileSpy::FileSpyCallBackFunc FileSpy::s_fileSpyCallBackFunc = NULL;

FileSpy *FileSpy::GetFileSpy()
{
	//Singleton get the only object
	if (s_fileSpy == NULL)
	{
		s_fileSpy = new FileSpy();
	}

	return s_fileSpy;
}

void FileSpy::SpyThread(void* arg)	
{
	//Create file handle
	wchar* pDrive = (wchar*)arg;
	HANDLE hDir = CreateFile(
		(wchar*)arg,
		GENERIC_READ|GENERIC_WRITE,
		FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_BACKUP_SEMANTICS,
		NULL
		);

	string wstrDir = pDrive;
	delete[] pDrive;

	if(INVALID_HANDLE_VALUE == hDir) 
	{
		return;
	}

	byte buffer[2 * (sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH)];
	FILE_NOTIFY_INFORMATION* pNotify=(FILE_NOTIFY_INFORMATION *)buffer;
	DWORD BytesReturned;

	while(true)
	{
		if( ReadDirectoryChangesW( hDir,
			pNotify,
			sizeof(buffer),
			true,
			FILE_NOTIFY_CHANGE_FILE_NAME|
			FILE_NOTIFY_CHANGE_DIR_NAME|
			FILE_NOTIFY_CHANGE_ATTRIBUTES|
			FILE_NOTIFY_CHANGE_SIZE|
			FILE_NOTIFY_CHANGE_LAST_WRITE|
			FILE_NOTIFY_CHANGE_LAST_ACCESS|
			FILE_NOTIFY_CHANGE_CREATION|
			FILE_NOTIFY_CHANGE_SECURITY,
			&BytesReturned,
			NULL,
			NULL ) )
		{
			string wstrOldName = wstrDir + pNotify->FileName;
			string wstrNewName;
			
			//Check next notify change
			if( 0 != pNotify->NextEntryOffset )
			{
				PFILE_NOTIFY_INFORMATION pNextNotify = (PFILE_NOTIFY_INFORMATION)((char*)pNotify + pNotify->NextEntryOffset);
				wstrNewName = wstrDir + pNextNotify->FileName;
			}

			//Set the notify type
			FILE_SPY_NOTIFY_TYPE fileNotifyType = FILE_SPY_NONE;

			if (pNotify->Action == FILE_ACTION_ADDED)
			{
				fileNotifyType = FILE_SPY_ADDED;
			}
			else if (pNotify->Action == FILE_ACTION_RENAMED_OLD_NAME || pNotify->Action == FILE_ACTION_RENAMED_NEW_NAME)
			{
				fileNotifyType = FILE_SPY_RENAME;
			}
			else if (pNotify->Action == FILE_ACTION_REMOVED)
			{
				fileNotifyType = FILE_SPY_REMOVED;
			}
			else if (pNotify->Action == FILE_ACTION_MODIFIED)
			{
				fileNotifyType = FILE_SPY_MODIFIED;
			}

			//Call the callback function
			if (s_fileSpyCallBackFunc != NULL && fileNotifyType != FILE_SPY_NONE)
			{
				s_fileSpyCallBackFunc(wstrOldName, fileNotifyType, wstrNewName);
			}
		}
		else
		{
			break;
		}
	}
}

void FileSpy::BeginSpy(FileSpyCallBackFunc fileSpyCallBackFunc)
{
	//Set the spy function
	s_fileSpyCallBackFunc = fileSpyCallBackFunc;

	//Get logic drive string and create thread to spy
	wchar wcSysDir[128];    
	wchar wcTempDrive[4];     

	uint32 nStrLen = 0;   	 
	nStrLen = GetLogicalDriveStrings(128, wcSysDir);

	uint32 count = 0;

	while(true)
	{
		for(uint32 i = 0;i < 4;i++)
		{
			wcTempDrive[i] = wcSysDir[count + i];
		}

		count += 4;

		if(wcTempDrive[0] == NULL)
		{
			break;
		}

		//Create a thread to spy file system
		wchar* pDriveBuffer = new wchar[4];
		memcpy(pDriveBuffer, wcTempDrive, 4 * sizeof(wchar));
		HANDLE spyThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SpyThread, (void *)pDriveBuffer, 0, NULL);

		//Add thread handle to vector
		if (spyThread != NULL)
		{
			m_vecThreadHandle.push_back(spyThread);
		}
	}
}

void FileSpy::EndSpy()
{
	//Traverse the vector to terminate the thread and close the handle
	std::vector<HANDLE>::iterator iterThread = m_vecThreadHandle.begin();

	while (iterThread != m_vecThreadHandle.end())
	{
		if(*iterThread != NULL)
		{
			TerminateThread(*iterThread, 0);
		}

		CloseHandle(*iterThread);
		++iterThread;
	}

	m_vecThreadHandle.clear();
}
